# JS 一些练习题
# 常见练习题
# 1. 判断输出结果
var a = { n:1 }
var b = a
a.x = a = { n:2 }
console.log(a.x)
console.log(b.x)
// undefined
// Object{n:2}
2
3
4
5
6
7
8
# 2. 判断输出结果
var a = { n:1 }
var b = a
a = { n:2 }
a.x = {n:2}
console.log(a.x)
console.log(b.x)
//Object{n:2}
//undefined
2
3
4
5
6
7
8
9
# 3. fizzbuzz
// 第 1 种
let fizzbuzz = (a, b) => {
for (let i = a; i <= b; i++){
console.log(
i % 3 === 0 && i % 5 === 0 ? 'fizzbuzz' :
i % 3 === 0 ? 'fizz' :
i % 5 === 0 ? 'buzz' : i
)
}
}
fizzbuzz(1, 100)
// 第 2 种
let fizzbuzz2 = (a, b) => {
for (let i = a; i <= b; i++) {
console.log(
((i % 3 === 0 ? 'fizz' : '') + (i % 5 === 0 ? 'buzz' : '')) || i
)
}
}
fizzbuzz2(1, 100)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4. 判断输出结果
function fn (a) {
return (a) => {
console.log(1, a)
return (a) => {
console.log(2, a)
}
}
}
console.log(fn()()('value'))
/*
1 undefined
2 "value"
undefined
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 5. 数组去重
let arr = [1, 1, true, true, '123', '123', undefined, undefined, null, null]
//ES6 方法:
console.log([...new Set(arr)]) // [1 true '123' undefined null]
console.log([...new Set(arr)]) // [1 true '123' undefined null]
// 适用于包含基本类型的数组:undefined、null、boolean、string和number。
// 如果数组中包含了一个object,function或其他数组,那就需要使用另一种方法。
// ES5 方法:
console.log(
arr.filter((item, i) => arr.indexOf(item) === i)
)
// 当前元素的下标 等于 此元素在元素组中第一个下标,则保留当前元素。其余的则被排除。
2
3
4
5
6
7
8
9
10
11
12
13
# 6. 获取两个数组的交集、并集等
// 交集
let arr1 = [1, 2, 3, 4]
let arr2 = [1, 3, 5, 7]
let jiaoji = arr1.filter(item => arr2.includes(item))
console.log(jiaoji) // [ 1, 3 ]
2
3
4
5
# 7. 如何让 a == 1 && a == 2 && a == 3 为true
// 重写劫持 Object.prototype.valueOf() 方法
var a = {
arr: [1, 2, 3],
valueOf () {
return this.arr.shift()
}
}
console.log(
a == 1, // true
a == 2, // true
a == 3 // true
)
// 重写劫持 Object.prototype.toString() 方法
var b = {
arr: [1, 2, 3],
toString () {
return this.arr.shift()
}
}
console.log(
b == 1, // true
b == 2, // true
b == 3 // true
)
// ES6 中,可以重写劫持 [Symbol.toPrimitive]() 方法
var c = {
arr: [1, 2, 3],
[Symbol.toPrimitive] () {
return this.arr.shift()
}
}
console.log(
c == 1, // true
c == 2, // true
c == 3 // true
)
// 利用重写 Array.prototype.join() 实现
var arr5 = [1, 2, 3];
arr5.join = arr5.shift
console.log(
arr5 == 1, // true
arr5 == 2, // true
arr5 == 3, // true
)
// 劫持对象的 getter 实现
var ALL = null
try { // 兼容浏览器 和 Node 的写法
ALL = window;
} catch (err) {
ALL = global;
}
ALL.value = 0;
Object.defineProperty(ALL, 'a', {
get () {
return ++this.value
}
})
console.log(
a == 1, // true
a == 2, // true
a == 3, // true
a == 4, // true
a == 5, // true
)
// 通过ES6 的 Proxy 代理 劫持 getter
let a = new Proxy({}, {
value: 0,
get () {
return () => {
return ++this.value
}
}
})
console.log(
a == 1,
a == 2,
a == 3,
a == 4,
a == 5,
)
// 使用对象中的正则
// 原理
var reg = /\d/g
console.log(reg.exec(123)) // [ '1', index: 0, input: '123', groups: undefined ]
console.log(reg.exec(123)) // [ '2', index: 1, input: '123', groups: undefined ]
console.log(reg.exec(123)) // [ '3', index: 2, input: '123', groups: undefined ]
console.log(reg.exec(123)) // null
// 实现
var a = {
reg: /\d/g,
valueOf () {
return this.reg.exec(123)[0]
}
}
console.log(
a == 1,
a == 2,
a == 3,
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 7.2 如何让 a === 1 && a === 2 && a === 3
返回 true
var value = 0
Object.defineProperty(window, 'a', {
get: function () {
return this.value += 1
}
})
console.log(a === 1 && a === 2 && a === 3)
2
3
4
5
6
7
8
# 8. 数组映射
const arr = [
{
uname: 'Tom',
age: '18'
},
{
uname: 'Jerry',
age: '17'
},
{
uname: 'Bob',
age: '20'
},
]
// 使用 map() 循环
console.log(arr.map(item => item.uname)) // [ 'Tom', 'Jerry', 'Bob' ]
// Array.from(arr, mapFunc, thisObj) 的mapFunc 回调函数自带循环效果,相当于 map()
console.log(Array.from(arr, item => item.uname)) // [ 'Tom', 'Jerry', 'Bob' ]
console.log(Array.from(arr, ({uname}) => uname)) // [ 'Tom', 'Jerry', 'Bob' ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 9. 对浮点数快速取整(截取小数点前的数)
使用 |
运算符
console.log(1.9 | 0) // 1
console.log(1.1 | 0) // 1
console.log(-1.9 | 0) // -1
console.log(-1.9 | 0) // -1
// 同样地,可以快速获取整数的前几位
console.log(1553 / 10 | 0) // 155
console.log(1553 / 100 | 0) // 15
console.log(1553 / 1000 | 0) // 1
2
3
4
5
6
7
8
9
# 10. 输出结果
['1', '2', '3'].map(parseInt)
// [ 1, NaN, NaN ]
2
3
4
// 解析:
// 数组实例的 map() 方法 接收的第一个参数是回调函数,并且传递三个参数给回调函数
arr.map((item, index, arr) => {}, thisArg)
// 上例将 parseInt() 函数作为了回调函数,所以相当于会循环执行
parseInt(item, index, arr)
// 而 parseInt() 接收两个参数,第一个待取整项,第二个为指定第一个参数是多少进制的基数(2~36)
parseInt(str, radix)
// 若 str 不是字符串,转为字符串,toString抽象操作,开头的空白符会被忽略。
// 若 radix 为 undefined ,0,或没有指定的情况下,JavaScript 作如下处理:
// 如果字符串 string 以"0x"或者"0X"开头, 则基数是16 (16进制).
// 如果字符串 string 以"0"开头, 基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决定。
// ECMAScript 5 规定使用10,建议明确给出radix参数的值。
// 如果字符串 string 以其它任何值开头,则基数是10 (十进制)。
// 所以相当于执行了
['1', '2', '3'].map((item, index) => {
return parseInt(item, index)
})
// 函数体内
parseInt('1', 0) // 1 // radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。
parseInt('2', 1) // NaN // 基数值必须位 2 ~ 36,指定为 1 无法解析
parseInt('3', 2) // NaN // 指定基数为2(2进制),但第一个参数不是2进制,所以无法解析。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 同理,另一种:
['10','10','10','10','10'].map(parseInt);
// [10, NaN, 2, 3, 4]
2
3
4
5
# 11. setTimeout、Promise、Async/Await 的区别
# 12. 异步执行结果输出顺序
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/*
script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 13. React setState() 执行顺序问题
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
// 0 0 2 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
原因: 在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。
2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。
3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。
输出: 0 0 2 3
# 14. ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
和 ['A', 'B', 'C', 'D']
,合并为 [ 'A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D' ]
const arr1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
const arr2 = ['A', 'B', 'C', 'D']
let res = [].concat(
...arr2.map(item2 => {
return arr1.filter(item1 => item1.startsWith(item2)).sort((a, b) => a - b).concat([item2])
})
)
console.log(res)
// [ 'A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D' ]
2
3
4
5
6
7
8
9
10
11
12
13
# 14. setTimeOut() 中输出 0 ~ 9 而不是 十个 10
for (var i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 10)
}
// 10 10 10 10 10 10 10 10 10 10
// 方法一:
for (let i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 10)
}
// 方法二:
for (var i = 0; i< 10; i++){
((i) => {
setTimeout(() => {
console.log(i);
}, 10)
})(i)
}
for (var i = 0; i< 10; i++){
setTimeout(((i) => () => console.log(i))(i), 10)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 15. 以下分别输出什么
var a = 10;
function foo() {
console.log(a); // ??
var a = 20;
}
foo();
// undefined // var a = 20 变量提升
2
3
4
5
6
7
8
var a = 10;
function foo() {
console.log(a); // ??
let a = 20;
}
foo();
// ReferenceError: a is not defined // let a = 20 形成暂时性死区,访问 a 报错
2
3
4
5
6
7
8
# 16. 闭包:newArray 的值
var array = [];
for(var i = 0; i <3; i++) {
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??
// [ 3, 3, 3 ]
2
3
4
5
6
7
8
var array = [];
for(let i = 0; i <3; i++) {
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??
// [ 0, 1, 2 ]
2
3
4
5
6
7
8
# 17. 如果我们在浏览器控制台中运行foo
函数,是否会导致堆栈溢出错误
function foo() {
setTimeout(foo, 0); // 是否存在堆栈溢出错误?
};
2
3
不会溢出
调用 foo()会将foo函数放入调用堆栈(call stack)。
在处理内部代码时,JS引擎遇到setTimeout。
然后将foo回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空
计时器被设置为0,因此foo将被发送到任务队列
<Task Queue>
(箭头2)。由于调用堆栈是空的,事件循环将选择foo回调并将其推入调用堆栈进行处理。
进程再次重复,堆栈不会溢出。
# 16. 如果在控制台中运行以下函数,页面(选项卡)的 UI 是否仍然响应
function foo() {
return Promise.resolve().then(foo);
};
foo()
2
3
4
不会响应
JavaScript中有宏任务和微任务。setTimeout回调是宏任务,而Promise回调是微任务。
主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行后返回到事件循环之前清空。因此,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面
每次调用 foo()
都会继续在微任务队列上添加另一个 foo
回调,因此事件循环无法继续处理其他事件(滚动,单击等),直到该队列完全清空为止。因此,它会阻止渲染。
# 17. 使用展开运算符(...
)遍历对象的值
var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError
2
展开语法 和 for-of 语句遍历iterable对象定义要遍历的数据。Array 或Map 是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用iterable和iterator协议使它们可迭代。
通过对象的 [Symbol.iterator]()
方法设置遍历
var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function() {
// iterator 是一个具有 next() 方法的对象,
// 它的返回至少有一个对象
// 两个属性:value&done。
// 返回一个 iterator 对象
return {
next: function() {
if (this._countDown === 3) {
const lastValue = this._countDown;
return { value: this._countDown, done: true };
}
this._countDown = this._countDown + 1;
return { value: this._countDown, done: false };
},
_countDown: 0
};
};
console.log([...obj]); // [ 1, 2, 3 ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用生成器函数 generator
也可以:
var obj = { x: 1, y: 2, z: 3}
obj[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
}
console.log([...obj]); // 打印 [1, 2, 3]
2
3
4
5
6
7
8
# 18. 运行以下代码片段时,控制台上会打印什么
var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, {c: 3});
Object.defineProperty(obj, 'd', { value: 4 });
for(let key in obj) {
console.log(key);
}
// a b c
2
3
4
5
6
7
8
9
for...in
循环遍历对象本身的可枚举属性以及对象从其原型继承的属性。
var obj = { a: 1, b: 2 }; //a,b 的属性 enumerable: true
// 将{c:3}设置为'obj'的原型,obj 会继承,for-in 循环也迭代继承的属性
Object.setPrototypeOf(obj, { c: 3 });
// defineProperty 设置的属性 默认 enumerable: false
Object.defineProperty(obj, "d", { value: 4 });
console.log(Object.getOwnPropertyDescriptor(obj, 'd'))
/*
{
value: 4,
writable: false,
enumerable: false,
configurable: false
}
*/
for (let prop in obj) {
console.log(prop);
}
// a
// b
// c
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 19. 判断输出结果
var x = 10;
var foo = {
x: 90,
getX: function() {
console.log(this.x)
}
};
foo.getX();
var xGetter = foo.getX;
xGetter();
var xGetter2 = foo.getX.bind(foo);
xGetter2();
// 90 // this => foo
// 10 // this => window
// 90 // this 被绑定到指向 foo 对象
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 20. 创建一个函数来判断给定的表达式中的大括号是否闭合,返回 true | false
,对于空字串,返回 true
若仅仅查找各种括号的个数,这种括号是不匹配的:
}{
let expression = "{{}}{{}{}}{}"
let expressionFalse = "{}}{{}"
function isBalanced(exp){
let info = exp.split("")
let stack = []
for(let i = 0; i < info.length; ++i){
let el = info[i]
if (el == "{"){
stack.push("{")
}
else if (el == "}"){
if(stack.length === 0){
return false
}
stack.pop()
}
}
return stack.length === 0
}
console.log(
isBalanced(expression)
)
console.log(
isBalanced(expressionFalse)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 升级:判断三种类型的括号 {}
,[]
,和 ()
是否达到闭合平衡
let exp1 = '(foo { bar (baz) [boo] })'
let exp2 = 'foo { bar { baz }'
let exp3 = 'foo { (bar [baz] } )'
const isBalanced = (str) => {
const map = new Map([
["{", "}"],
["[", "]"],
["(", ")"],
])
let stack = []
for(let i = 0 ; i < str.length; ++i){
let node = str[i]
if (map.has(node)){
stack.push(node)
}
else if ([...map.values()].includes(node)){
if (
stack[stack.length - 1] !== [...map.entries()].filter(el=>el[1]===node).pop().shift()
){
return false
}
stack.splice(stack.length-1, 1)
}
}
return stack.length === 0
}
console.log(isBalanced(exp1)) // true
console.log(isBalanced(exp2)) // false
console.log(isBalanced(exp3)) // false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# this 问题
let fn = function () {
console.log(this)
}
let obj = { fn }
(fn, obj.fn)() // window ,(fn, obj.fn) 返回右侧,但此时已经不是 obj 在调用它
(obj.fn)() // obj
2
3
4
5
6
7
8
# 判断输出结果
function Foo() {
getName = function () { console.log(1); };
console.log(this)
return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4);};
function getName() { console.log(5);}
//请写出以下输出结果:
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
// 注意点:
// new Foo().getName() 相当于
new (Foo.getName)();
// 也相当于
var a = new Foo()
a.getName()
// new new Foo().getName() 相当于
new ((new Foo()).getName)();
// 也相当于
var a = new Foo()
var b = a.getName
var c = new b() // 其实第一个 new 和最后的 () 才是配对的
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function Foo() {
this.getName = function() {
console.log(3);
return {
getName: getName //这个就是第六问中涉及的构造函数的返回值问题
}
}; //这个就是第六问中涉及到的,JS构造函数公有方法和原型链方法的优先级
getName = function() {
console.log(1);
};
return this
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function() {
console.log(6);
};
var getName = function() {
console.log(4);
};
function getName() {
console.log(5);
} //答案:
Foo.getName(); //2
getName(); //4
console.log(Foo())
Foo().getName(); //1
getName(); //1
new Foo.getName(); //2
new Foo().getName(); //3
//多了一问
new Foo().getName().getName(); //3 1
new new Foo().getName(); //3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35